#include <stdio.h>
#include <string.h>
#include <time.h>
#include "email.h"
#include "quickmail.h"
#include "utilities.h"
#include "ini.h"
#include "download_file.h"
#include "date.h"
#include "rss.h"

///////////////////////////////////////////////////////////////////////////////

//#define FILE_TRACE

#include "file_trace.h"

///////////////////////////////////////////////////////////////////////////////

//#define DEBUG

enum
{
    MESSAGE_ID_LENGTH = 32
};

static char message_id[MESSAGE_ID_LENGTH] = "";
static char subject[HEADER_FIELD_BUFFER_LENGTH] = "";
static char header_field_value[HEADER_FIELD_BUFFER_LENGTH];

static bool data_received = false;

struct MemoryStruct {
  char *memory;
  size_t size;
};

bool EMAIL_Send(char * sender_address, char * recipient_address, char * subject,
    char * message_id, char * body, char ** attachments, format_opt_t format,
    char * smtp_server, int smtp_port, char * username, char * password)
{
    bool success = false;
    const char* errmsg;
    int i = 0;
	quickmail mailobj;

    if (sender_address == NULL || recipient_address == NULL)
    {
        F_ERROR(("Invalid sender or recipient address: sender '%s', recipient '%s'\n",
            sender_address, recipient_address));
        return false;
    }

    if (format != format_opt_text && format != format_opt_html)
    {
        F_ERROR(("Invalid email format %d\n", format));
        return false;
    }

	printf("\nSending email... (recipient '%s', Message-ID '%s')\n", recipient_address, message_id);

    mailobj = quickmail_create(sender_address, subject);
    quickmail_add_to(mailobj, recipient_address);
    quickmail_set_message_id(mailobj, message_id);
    quickmail_set_format(mailobj, format);
    quickmail_set_body(mailobj, body);
    
    if (attachments != NULL)
    {
        while (attachments[i] != NULL)
        {
            quickmail_add_attachment_file(mailobj, attachments[i]);
            ++i;
        }
    }

#ifdef DEBUG
    // WARNING: calling quickmail_fsave causes the bodies of sent emails to disappear
    //quickmail_fsave(mailobj, stdout);
 
    quickmail_set_debug_log(mailobj, stderr);
#endif
    
    errmsg = quickmail_send(mailobj, smtp_server, smtp_port, username, password);
    if (errmsg == NULL)
    {
        printf("Email sent.\n\n"); 
        success = true;
    }
    else
    {
        fprintf(stderr, "Error sending email: %s\n", errmsg);    
    }

    quickmail_destroy(mailobj);

    return success;
}

char * EMAIL_GetLastGeneratedMessageId()
{
    return message_id;
}

char * EMAIL_GenerateRandomMessageId()
{
    int i;
    static const char alphabet[] = "abcdefghijklmnopqrstuvwxyz";

    message_id[0] = '<';

    for (i = 1; i < sizeof(message_id) - sizeof(MESSAGE_ID_SUFFIX) - 2; ++i)
    {
        message_id[i] = alphabet[rand() % (sizeof(alphabet) - 1)];
    }
    
    message_id[i] = '\0';

    strcat(message_id, MESSAGE_ID_SUFFIX);

    return message_id;
}

char * EMAIL_GenerateDigestEmailSubject()
{
    char * date_time = DATE_GetSystemDateTime();

    sprintf(subject, DIGEST_EMAIL_SUBJECT_FORMAT, date_time);
    
    free(date_time);

    return subject;
}

size_t
EMAIL_SaveAllReceivedDatatoMemory(void *contents, size_t size, size_t nmemb, void *userp)
{
    size_t realsize = size * nmemb;
    struct MemoryStruct *mem = (struct MemoryStruct *)userp;

    data_received = true;
#ifdef DEBUG
    printf("%s: contents %p, size %6d, nmemb %6d, userp %p\n", __FUNCTION__,
        contents, size, nmemb, userp);
#endif

    mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1);
    if (mem->memory == NULL) {
        /* out of memory! */
        printf("not enough memory (realloc returned NULL)\n");
        exit(EXIT_FAILURE);
    }

    memcpy(&(mem->memory[mem->size]), contents, realsize);
    mem->size += realsize;
    mem->memory[mem->size] = 0;

    return realsize;
}

char * EMAIL_ExtractSubjectFromBuffer(char * buffer, char * field)
{
    int i = 0;
    char * ptr = strstr(buffer, field);
    
    if (ptr)
    {
        /* advance the pointer to the beginning of the Message-ID */
        ptr += strlen(field);

        /* copy the characters until reaching a delimiter */
        while (*ptr != '=')
        {
            if(*ptr != '\r' && *ptr != '\n')
            {
                header_field_value[i] = *ptr;
                ++i;
            }
            ++ptr;
        }
    }

    header_field_value[i] = '\0';

    return header_field_value;
}

static void curl_perform(CURL *curl)
{
    CURLcode res = CURLE_OK;
    int i;

    data_received = false;

    /* We normally could wait a fixed amount of time to let the operation finish.
       But to speed things up, just retry several times rather than waiting
       blindly. */
    for (i = 0; i < EMAIL_RECEPTION_MAX_CHECK_COUNT; ++i)
    {
        /* Perform the request, res will get the return code */
        res = curl_easy_perform(curl);

#ifdef DEBUG
        /* Check for errors. 
            Two common errors are "Timeout was reached" (CURLE_OPERATION_TIMEDOUT) and
            "Failure when receiving data from the peer" (CURLE_RECV_ERROR). */
        if(res != CURLE_OK)
        {
            char * timestamp = DATE_GetSystemDateTime();
            fprintf(stderr, "%s curl_easy_perform() failed: %s. data_received: %s\n",
                timestamp, curl_easy_strerror(res), data_received ? "true" : "false");
            free(timestamp);
        }
#endif

        /* if data reception fails, data_received will stay false */
        if (data_received == true)
        {
            break;
        }
    }
}

bool EMAIL_Receive(struct MemoryStruct *sample_chunk, const char * mailbox_url, 
    const char * username, char * password)
{
    CURL *curl;
    char newest_message_url[sizeof(POP_MAILBOX_URL_TEST) + MAX_LENGTH_LAST_LINE_POP3_LIST];
    char * last_newline;
    char * last_space;

    sample_chunk->memory = (char *)malloc(1);  /* will be grown as needed by the realloc above */
    sample_chunk->size = 0;    /* no data at this point */

    curl_global_init(CURL_GLOBAL_ALL);

    curl = curl_easy_init();

    if(curl)
    {
        /* Set mailbox */
        curl_easy_setopt(curl, CURLOPT_URL, mailbox_url);

        /* Set username and password */
        curl_easy_setopt(curl, CURLOPT_USERNAME, username);
        curl_easy_setopt(curl, CURLOPT_PASSWORD, password);

        /* send all data to this function  */
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, EMAIL_SaveAllReceivedDatatoMemory);

        /* we pass our 'chunk' struct to the callback function */
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)sample_chunk);

        /* some servers don't like requests that are made without a user-agent
           field, so we provide one */
        curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");

        /* setting CURLOPT_CONNECTTIMEOUT doesn't seem to have a noticable effect */

        /* if the transfer operation takes longer than expected stop and retry it */
        curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, EMAIL_RECEIVE_DATA_TIMEOUT_MS); 

        /* Unlike gmail, for hMailServer /1 means not the last but the first message.
           So get the number of messages first and then use it as an index 
           to the last message. */
        if (strcmp(mailbox_url, POP_MAILBOX_URL_GMAIL) != 0)
        {
            /* list the mailbox contents */
            curl_perform(curl);

            if (data_received = false)
            {
                F_PRINT(("No data received!\n"));
                return false;
            }

            /* Now, our chunk.memory points to a memory block that is chunk.size
               bytes big and contains the retrieved data. */

    #ifdef DEBUG
            if (sample_chunk->size > 0)
            {
                printf(".....................................\n");
                printf("%s", sample_chunk->memory);
                printf(".....................................\n");
            }
    #endif
            /* if there are no messages in the mailbox return */
            if (sample_chunk->size == strlen("\r\n"))
            {
                F_PRINT(("Nothing to receive, empty mailbox!\n"));
                return false;
            }

            /* If there are two messages in the mailbox, the retrieved buffer
               will contain something like "1 312\r\n2 1814\r\n\0".
               Locate the begginning of the last line and append it to the
               mailbox URL. */
            strcpy(newest_message_url, mailbox_url);
            last_space = strrchr(sample_chunk->memory, ' ');
            *last_space = '\0';
            last_newline = strrchr(sample_chunk->memory, '\n');
            strcat(newest_message_url, last_newline ? last_newline + 1 : sample_chunk->memory);

    #ifdef DEBUG
            printf("newest_message_url '%s'\n", newest_message_url);
    #endif
            /* Set mailbox */
            curl_easy_setopt(curl, CURLOPT_URL, newest_message_url);

            /* invalidate previously received data */
            sample_chunk->size = 0;
        }

        printf("\nAttempting to receive sent email...\n");

        /* retrieve the last message */
        curl_perform(curl);

        /* Now, our chunk.memory points to a memory block that is chunk.size
           bytes big and contains the retrieved data. */

#ifdef DEBUG
        if (sample_chunk->size > 0)
        {
            printf("-------------------------------------\n");
            printf("%s", sample_chunk->memory);
            printf("-------------------------------------\n");
        }
#endif

        /* always cleanup */
        curl_easy_cleanup(curl);
    }

    /* we're done with libcurl, so clean it up */
    curl_global_cleanup();

    return data_received;
}

void EMAIL_ExtractCommandData()
{
    struct MemoryStruct chunk;
	char* data_to_add;

	printf("\nExtract commands from email subject:");

	if(EMAIL_Receive(&chunk, POP_MAILBOX_URL_GMAIL, RECIPIENT_USERNAME_GMAIL,
        RECIPIENT_PASSWORD_GMAIL))
	{
	    data_to_add = EMAIL_ExtractSubjectFromBuffer(chunk.memory, "AddFeed ");

		if (*data_to_add != '\0')
		{
            if(!EMAIL_WriteDataToINIFile("feeds", data_to_add))
            {
                F_PRINT(("Failed to write commands to ini file!\n"));
            }
		}
		else
		{
			data_to_add = EMAIL_ExtractSubjectFromBuffer(chunk.memory, "AddCustomer ");

            if(*data_to_add != '\0')
            {
                if(!EMAIL_WriteDataToINIFile("customers", data_to_add))
                {
                    F_PRINT(("Failed to write commands to ini file!\n"));
                }
            }
            else
            {
                F_PRINT(("There is no current command to ini file!\n"));
            }
		}    
	}
}

bool EMAIL_WriteDataToINIFile(char* section_name, char* command_data)
{
	const char* ini_path = INI_GetIniPath();
    bool result = false;

	if(command_data != NULL)
	{
		WritePrivateProfileString(section_name, command_data, "", ini_path);
        result = true;
    }

    return result;
}


bool EMAIL_SendDigests(int customer_count, const customer_t * customers)
{
    int i;
    bool result = false;

    for (i = 0; i < customer_count; i++)
    {
        result = EMAIL_Send(SENDER_ADDRESS_GMAIL, 
            customers[i].email, 
            EMAIL_GenerateDigestEmailSubject(),
            EMAIL_GenerateRandomMessageId(),
            customers[i].formatted_digest,
            NULL,
            customers[i].format_opt,
            SMTPSERVER_GMAIL,
            SMTPPORT_GMAIL,
            SENDER_USERNAME_GMAIL,
            SENDER_PASSWORD_GMAIL);

        if (result == false)
        {
            F_ERROR(("failed to send digest email!\n"));
        }
    }

    return result;
}

static char * EMAIL_GenerateStatisticsEmailSubject()
{
    sprintf(subject, STATISTICS_EMAIL_SUBJECT_FORMAT, DATE_GetPreviousMonthAndYearString());
    
    return subject;
}

bool EMAIL_SendStatistics(int customer_count, const customer_t * customers)
{
    int i;
    bool result = false;
    char * attachment[] = {RSS_GetStatisticsFilePath(), NULL};
    
    for (i = 0; i < customer_count; i++)
    {
        result = EMAIL_Send(SENDER_ADDRESS_GMAIL,
            customers[i].email, 
            EMAIL_GenerateStatisticsEmailSubject(),
            EMAIL_GenerateRandomMessageId(),
            STATISTICS_EMAIL_BODY,
            attachment,
            customers[i].format_opt,
            SMTPSERVER_GMAIL,
            SMTPPORT_GMAIL,
            SENDER_USERNAME_GMAIL,
            SENDER_PASSWORD_GMAIL);

        if (result == false)
        {
            F_ERROR(("failed to send digest email!\n"));
        }
    }

    free(*attachment);

    return result;
}
